package com.daikit.daikit4gxt.client.ui.editor.field;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import com.daikit.commons.shared.utils.DkCollectionUtils;
import com.daikit.daikit4gxt.client.DkBaseMain;
import com.daikit.daikit4gxt.client.labelprovider.DkAsSimpleStringLabelProvider;
import com.daikit.daikit4gxt.client.ui.UIInvalidatable;
import com.daikit.daikit4gxt.client.ui.component.DkSpacer;
import com.daikit.daikit4gxt.client.ui.edit.AddButtonSelectEvent;
import com.daikit.daikit4gxt.client.ui.edit.AddButtonSelectEvent.AddButtonSelectHandler;
import com.daikit.daikit4gxt.client.ui.edit.AddButtonSelectEvent.HasAddButtonSelectHandlers;
import com.daikit.daikit4gxt.client.ui.edit.CreateButtonSelectEvent;
import com.daikit.daikit4gxt.client.ui.edit.CreateButtonSelectEvent.CreateButtonSelectHandler;
import com.daikit.daikit4gxt.client.ui.edit.CreateButtonSelectEvent.HasCreateButtonSelectHandlers;
import com.daikit.daikit4gxt.client.ui.edit.DeleteButtonSelectEvent;
import com.daikit.daikit4gxt.client.ui.edit.DeleteButtonSelectEvent.DeleteButtonSelectHandler;
import com.daikit.daikit4gxt.client.ui.edit.DeleteButtonSelectEvent.HasDeleteButtonSelectHandlers;
import com.daikit.daikit4gxt.client.ui.edit.EditButtonSelectEvent;
import com.daikit.daikit4gxt.client.ui.edit.EditButtonSelectEvent.EditButtonSelectHandler;
import com.daikit.daikit4gxt.client.ui.edit.EditButtonSelectEvent.HasEditButtonSelectHandlers;
import com.daikit.daikit4gxt.client.ui.editor.DkEditorFunctionality;
import com.daikit.daikit4gxt.client.ui.editor.advanced.AbstractDkHideableAdapterField;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.sencha.gxt.data.shared.LabelProvider;
import com.sencha.gxt.widget.core.client.button.TextButton;
import com.sencha.gxt.widget.core.client.container.HBoxLayoutContainer;
import com.sencha.gxt.widget.core.client.event.SelectEvent;


/**
 * A field with edition/delete/create/add functionalities. Typically to be used for OneToOne relations.
 *
 * @author tcaselli
 * @version $Revision$ Last modifier: $Author$ Last commit: $Date$
 * @param <T>
 */
public class DkFieldWithEditor<T> extends AbstractDkHideableAdapterField<T> implements HasAddButtonSelectHandlers, HasCreateButtonSelectHandlers,
		HasDeleteButtonSelectHandlers<T>, HasEditButtonSelectHandlers<T>, UIInvalidatable
{

	private TextButton buttonAdd = null;
	private TextButton buttonCreate = null;
	private TextButton buttonEdit = null;
	private TextButton buttonDelete = null;
	private boolean enabled = true;

	private final DkReadOnlyField<T> field;

	private int height = 26;
	private final int buttonDefaultWidth = 30;
	private final int buttonDefaultWidthWithLabel = 75;

	private final Map<TextButton, Integer> buttons = new HashMap<TextButton, Integer>();

	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
	// CONSTRUCTOR
	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

	/**
	 * Create a new {@link DkReadOnlyField} with a {@link DkAsSimpleStringLabelProvider}
	 *
	 * @param showFunctionalitiesLabels
	 *           boolean indicating whether to show functionalities labels
	 * @param functionalities
	 *           editor functionalities (optional, by default no functionality is provided)
	 */
	public DkFieldWithEditor(final boolean showFunctionalitiesLabels, final DkEditorFunctionality... functionalities)
	{
		this(new DkAsSimpleStringLabelProvider<T>(), showFunctionalitiesLabels, functionalities);
	}

	/**
	 * Constructor
	 *
	 * @param showFunctionalitiesLabels
	 *           boolean indicating whether to show functionalities labels
	 * @param labelProvider
	 *           the {@link LabelProvider}
	 * @param functionalities
	 *           editor functionalities (optional, by default no functionality is provided)
	 */
	public DkFieldWithEditor(final LabelProvider<T> labelProvider, final boolean showFunctionalitiesLabels, final DkEditorFunctionality... functionalities)
	{
		this(labelProvider, showFunctionalitiesLabels, DkCollectionUtils.asSet(functionalities));
	}

	/**
	 * Constructor
	 *
	 * @param showFunctionalitiesLabels
	 *           boolean indicating whether to show functionalities labels
	 * @param labelProvider
	 *           the {@link LabelProvider}
	 * @param functionalities
	 *           editor functionalities (optional, by default no functionality is provided)
	 */
	public DkFieldWithEditor(final LabelProvider<T> labelProvider, final boolean showFunctionalitiesLabels,
			final Collection<DkEditorFunctionality> functionalities)
	{
		super(new HBoxLayoutContainer());

		if (functionalities.contains(DkEditorFunctionality.ADD))
		{
			buttonAdd = new TextButton(showFunctionalitiesLabels ? DkBaseMain.i18n().label_add() : "", new SelectEvent.SelectHandler()
			{
				@Override
				public void onSelect(final SelectEvent event)
				{
					fireEvent(new AddButtonSelectEvent());
				}
			})
			{
				@Override
				public void setWidth(final int width)
				{
					super.setWidth(width);
					buttons.put(buttonAdd, width);
				}
			};
			buttonAdd.setIcon(DkBaseMain.icons().file_plus_16());
			buttons.put(buttonAdd, showFunctionalitiesLabels ? buttonDefaultWidthWithLabel : buttonDefaultWidth);
		}
		if (functionalities.contains(DkEditorFunctionality.CREATE))
		{
			buttonCreate = new TextButton(showFunctionalitiesLabels ? DkBaseMain.i18n().label_create() : "", new SelectEvent.SelectHandler()
			{
				@Override
				public void onSelect(final SelectEvent event)
				{
					fireEvent(new CreateButtonSelectEvent());
				}
			})
			{
				@Override
				public void setWidth(final int width)
				{
					super.setWidth(width);
					buttons.put(buttonCreate, width);
				}
			};
			buttonCreate.setIcon(DkBaseMain.icons().file_plus_16());

			buttons.put(buttonCreate, showFunctionalitiesLabels ? buttonDefaultWidthWithLabel : buttonDefaultWidth);
		}
		if (functionalities.contains(DkEditorFunctionality.EDIT))
		{
			buttonEdit = new TextButton(showFunctionalitiesLabels ? DkBaseMain.i18n().label_edit() : "", new SelectEvent.SelectHandler()
			{
				@Override
				public void onSelect(final SelectEvent event)
				{
					fireEvent(new EditButtonSelectEvent<T>(getValue()));
				}
			})
			{
				@Override
				public void setWidth(final int width)
				{
					super.setWidth(width);
					buttons.put(buttonEdit, width);
				}
			};
			buttonEdit.setIcon(DkBaseMain.icons().file_edit_16());

			buttons.put(buttonEdit, showFunctionalitiesLabels ? buttonDefaultWidthWithLabel : buttonDefaultWidth);
		}

		if (functionalities.contains(DkEditorFunctionality.DELETE))
		{
			buttonDelete = new TextButton(showFunctionalitiesLabels ? DkBaseMain.i18n().label_delete() : "", new SelectEvent.SelectHandler()
			{
				@Override
				public void onSelect(final SelectEvent event)
				{
					fireEvent(new DeleteButtonSelectEvent<T>(getValue()));
				}
			})
			{
				@Override
				public void setWidth(final int width)
				{
					super.setWidth(width);
					buttons.put(buttonEdit, width);
				}
			};
			buttonDelete.setIcon(DkBaseMain.icons().delete2_16());

			buttons.put(buttonDelete, showFunctionalitiesLabels ? buttonDefaultWidthWithLabel : buttonDefaultWidth);
		}

		// START CREATING UI

		final HBoxLayoutContainer line = (HBoxLayoutContainer) getWidget();
		line.getElement().getStyle().setMarginBottom(2, Unit.PX);

		field = new DkReadOnlyField<T>(labelProvider);
		line.add(field);

		setHeight(height);

		for (final Entry<TextButton, Integer> entry : buttons.entrySet())
		{
			line.add(new DkSpacer(SPACERWIDTH));
			line.add(entry.getKey());
			entry.getKey().setWidth(entry.getValue());
		}

		line.addResizeHandler(new ResizeHandler()
		{
			@Override
			public void onResize(final ResizeEvent event)
			{
				final int width = event.getWidth();
				if (width > 0)
				{
					field.setWidth(computeFieldWidth(width));
				}
			}
		});
	}

	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
	// METHODS
	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

	/**
	 * Enable or disable buttons
	 *
	 * @param enabled
	 */
	@Override
	public void setEnabled(final boolean enabled)
	{
		if (buttonAdd != null)
		{
			buttonAdd.setEnabled(enabled);
		}
		if (buttonCreate != null)
		{
			buttonCreate.setEnabled(enabled);
		}
		if (buttonEdit != null)
		{
			buttonEdit.setEnabled(enabled);
		}
		if (buttonDelete != null)
		{
			buttonDelete.setEnabled(enabled);
		}
		field.setEnabled(enabled);
		this.enabled = enabled;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.sencha.gxt.widget.core.client.Component#setWidth(int)
	 */
	@Override
	public void setWidth(final int width)
	{
		field.setWidth(computeFieldWidth(width));
		super.setWidth(width);
	}

	/**
	 * @param height
	 *           the height to set
	 */
	@Override
	public void setHeight(final int height)
	{
		this.height = height;
		field.setHeight(height);
		((HBoxLayoutContainer) getWidget()).setHeight(height);
	}

	/**
	 * Set the create button tooltip
	 *
	 * @param tooltip
	 *           the tooltip
	 */
	public void setCreateTooltip(final String tooltip)
	{
		if (buttonCreate != null)
		{
			buttonCreate.setTitle(tooltip);
		}
	}

	/**
	 * Set the edit button tooltip
	 *
	 * @param tooltip
	 *           the tooltip
	 */
	public void setEditTooltip(final String tooltip)
	{
		if (buttonEdit != null)
		{
			buttonEdit.setTitle(tooltip);
		}
	}

	/**
	 * Set the delete button tooltip
	 *
	 * @param tooltip
	 *           the tooltip
	 */
	public void setDeleteTooltip(final String tooltip)
	{
		if (buttonDelete != null)
		{
			buttonDelete.setTitle(tooltip);
		}
	}

	/**
	 * Set the add button tooltip
	 *
	 * @param tooltip
	 *           the tooltip
	 */
	public void setAddTooltip(final String tooltip)
	{
		if (buttonAdd != null)
		{
			buttonAdd.setTitle(tooltip);
		}
	}

	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
	// PRIVATE UTILS
	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

	private int computeFieldWidth(final int width)
	{
		int fieldWidth = width;
		for (final Integer buttonWidth : buttons.values())
		{
			fieldWidth -= buttonWidth + SPACERWIDTH;
		}
		return fieldWidth;
	}

	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
	// GETTERS / SETTERS
	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

	/**
	 * Set value without fire event
	 *
	 * @param value
	 *           the value to set
	 */
	@Override
	public void setValue(final T value)
	{
		field.setValue(value);
	}

	/**
	 * Set value
	 *
	 * @param value
	 *           the value to set
	 * @param fireEvent
	 *           true to fire event (false by default)
	 */
	public void setValue(final T value, final boolean fireEvent)
	{
		field.setValue(value, fireEvent);
	}

	/**
	 * @return the height
	 */
	public int getHeight()
	{
		return height;
	}

	/**
	 * @return the current value
	 */
	@Override
	public T getValue()
	{
		return field.getValue();
	}

	/**
	 * @return the embedded field
	 */
	public DkReadOnlyField<T> getField()
	{
		return field;
	}

	/**
	 * @return the enabled
	 */
	@Override
	public boolean isEnabled()
	{
		return enabled;
	}

	/**
	 * @return the buttonAdd
	 */
	public TextButton getButtonAdd()
	{
		return buttonAdd;
	}

	/**
	 * @return the buttonCreate
	 */
	public TextButton getButtonCreate()
	{
		return buttonCreate;
	}

	/**
	 * @return the buttonEdit
	 */
	public TextButton getButtonEdit()
	{
		return buttonEdit;
	}

	/**
	 * @return the buttonDelete
	 */
	public TextButton getButtonDelete()
	{
		return buttonDelete;
	}

	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
	// METHODS TO BE OVERRIDDEN
	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

	protected boolean isModelCreateable(final T model)
	{
		return true;
	}

	protected boolean isModelEditable(final T model)
	{
		return true;
	}

	protected boolean isModelDeletable(final T model)
	{
		return true;
	}

	protected boolean isModelAddable(final T model)
	{
		return true;
	}

	@Override
	public void invalidateUi()
	{
		if (buttonCreate != null)
		{
			buttonCreate.setEnabled(isModelCreateable(getValue()) && getValue() == null);
		}
		if (buttonEdit != null)
		{
			buttonEdit.setEnabled(isModelEditable(getValue()) && getValue() != null);
		}
		if (buttonDelete != null)
		{
			buttonDelete.setEnabled(isModelDeletable(getValue()) && getValue() != null);
		}
		if (buttonAdd != null)
		{
			buttonAdd.setEnabled(isModelAddable(getValue()) && getValue() == null);
		}
	}

	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
	// SELECTION LISTENERS
	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

	@Override
	public HandlerRegistration addEditSelectHandler(final EditButtonSelectHandler<T> handler)
	{
		return addHandler(handler, EditButtonSelectEvent.getType());
	}

	@Override
	public HandlerRegistration addDeleteSelectHandler(final DeleteButtonSelectHandler<T> handler)
	{
		return addHandler(handler, DeleteButtonSelectEvent.getType());
	}

	@Override
	public HandlerRegistration addCreateSelectHandler(final CreateButtonSelectHandler handler)
	{
		return addHandler(handler, CreateButtonSelectEvent.getType());
	}

	@Override
	public HandlerRegistration addAddSelectHandler(final AddButtonSelectHandler handler)
	{
		return addHandler(handler, AddButtonSelectEvent.getType());
	}

}
